/*www.benjamin-dillenburger.com*/
package pGeom2D;

import java.awt.geom.Line2D;
import java.util.Vector;
import processing.core.PVector;

public class POrientedBoundingBox {

	PCaliper[] calipers;

	double[] angles;

	public PVector pMinX;

	public PVector pMinY;

	public PVector pMaxX;

	public PVector pMaxY;

	public Vector<PVector> convexHull;

	// int[] indexPoints;
	int[] indexPoints = new int[4];

	double[] deltaNextDist = new double[4];

	double[] angle = new double[4];

	double[] deltaAngle = new double[4];

	double rotAngle;

	PVector[] rect;

	double rectAngle;

	PVector rectOrigin;

	double minWidth;

	double minHeight;

	double minArea;

	public POrientedBoundingBox(Vector<PVector> convexHull) {
		calculateBoundingBox(convexHull);
	}

	 void calculateBoundingBox(Vector<PVector> convexHull) {
		this.convexHull = convexHull;
		PVector p0;
		PVector p2 = convexHull.get(0);
		PVector p1 = convexHull.lastElement();
		double area = PVecMath2D.getArea(convexHull);
		if (area > 0) {
			System.out.println("area");
		}
		for (int i = 0; i < this.convexHull.size(); i++) {
			p0 = p1;
			p1 = p2;
			p2 = this.convexHull.get((i + 1) % convexHull.size());
			PVector p = PVector.sub(p2, p1);
			double d = p.mag();
			if (d < 0.0001) {
				this.convexHull.remove(i);
			} else {
				double cAngle = PVecMath2D.getAngleBetweenCCW(p0, p1, p2);
				double dA = Math.abs(cAngle - Math.PI);
				if (dA < 0.01) {
					// System.out.println("same: " + dA);
					this.convexHull.remove(i);
				}

			}
		}
		// Collections.reverse(convexHull);
		this.calculateOBB();
	}

	void calculateOBB() {

		rect = new PVector[4];
		int minX = 0;
		int maxX = 0;
		int minY = 0;
		int maxY = 0;

		angles = new double[convexHull.size()];

		PVector p2 = convexHull.get(0);
		PVector p1;

		pMinX = p2;
		pMinY = p2;
		pMaxX = p2;
		pMaxY = p2;

		for (int i = 0; i < convexHull.size(); i++) {
			p1 = p2;
			p2 = convexHull.get((i + 1) % convexHull.size());
			PVector p = PVector.sub(p2, p1);
			angles[i] = PVecMath2D.getAngle(p);

			if (pMinX.x > p1.x) {
				pMinX = p1;
				minX = i;
			} else if (pMaxX.x < p1.x) {
				pMaxX = p1;
				maxX = i;
			}
			if (pMinY.y > p1.y) {
				pMinY = p1;
				minY = i;
			} else if (pMaxY.y < p1.y) {
				pMaxY = p1;
				maxY = i;
			}
		}
		minWidth = pMaxX.x - pMinX.x + 1;
		minHeight = pMaxY.y - pMinY.y + 1;
		minArea = minWidth * minHeight;

		calipers = new PCaliper[4];
		calipers[0] = new PCaliper(convexHull, angles, minY, 0);
		calipers[1] = new PCaliper(convexHull, angles, maxX, Math.PI * 0.5);
		calipers[2] = new PCaliper(convexHull, angles, maxY, Math.PI);
		calipers[3] = new PCaliper(convexHull, angles, minX, Math.PI * 1.5);

		/*
		 * calipers[0] = new Caliper(convexHull, angles, minY, Math.PI);
		 * calipers[1] = new Caliper(convexHull, angles, maxX, Math.PI * 0.5);
		 * calipers[2] = new Caliper(convexHull, angles, maxY, 0); calipers[3] =
		 * new Caliper(convexHull, angles, minX, Math.PI * 1.5);
		 */
		analyse();
		rotate();
	}

	void analyse() {
		double cWidth = calipers[0].getDistance(calipers[2].getPoint());
		double cHeight = calipers[1].getDistance(calipers[3].getPoint());
		double cArea = cWidth * cHeight;

		if (minArea > cArea) {
			minWidth = cWidth;
			minHeight = cWidth;
			minArea = cArea;
			rectAngle = calipers[0].angle;
			makeRect();
		}
	}

	void rotate() {
		int r = 0;
		while (calipers[0].angle > Math.PI * -1.5) {

			rotAngle = 1000;
			for (int i = 0; i < 4; i++) {
				PCaliper c = calipers[i];
				// System.out.println("delta"+c.deltaAngle);
				if (c.deltaAngle < rotAngle) {
					rotAngle = c.deltaAngle;
				}
			}

			for (int i = 0; i < 4; i++) {
				PCaliper c = calipers[i];
				c.rotate(rotAngle);
			}
			// System.out.println("calipers[0].angle: "+calipers[0].angle);
			analyse();
			r++;
		}

	}

	void makeRect() {
		rect[0] = PVecMath2D.lineLineIntersection(calipers[0].getLine2D(1),
				calipers[3].getLine2D(1));
		rect[1] = PVecMath2D.lineLineIntersection(calipers[1].getLine2D(1),
				calipers[0].getLine2D(1));
		rect[2] = PVecMath2D.lineLineIntersection(calipers[2].getLine2D(1),
				calipers[1].getLine2D(1));
		rect[3] = PVecMath2D.lineLineIntersection(calipers[3].getLine2D(1),
				calipers[2].getLine2D(1));
	}

	double getDist(PVector p1, PVector P2, double angle) {
		return 0;
	}

	public PVector[] getOrientedBoundingBox() {
		return rect;
	}

}

class PCaliper {
	public Vector<PVector> convexHull;

	public double[] angles;

	public PCaliper(Vector<PVector> convexHull, double[] angles, int index,
			double angle) {
		this.angles = angles;
		this.convexHull = convexHull;
		this.angle = angle;
		this.point = index;

		deltaAngle = PVecMath2D.getAngleBetweenCCW(angle, angles[point]);

		if (deltaAngle <= 0) {
			point = (point + 1) % convexHull.size();
			double nextDeltaAngle = PVecMath2D.getAngleBetweenCCW(angles[point],
					angle);
			deltaAngle = nextDeltaAngle;
		}
	}

	public PCaliper(int index, double angle) {
		this.angle = angle;
		this.point = index;
	}

	double angle;

	double deltaAngle;

	int point;

	PVector[] getLine2D(double l) {
		PVector[] pts = new PVector[2];
		pts[0] = convexHull.get(point);
		pts[1] = PVecMath2D.getPolar(pts[0], angle, l);
		return pts;
	}

	public double getDistance(PVector p) {
		PVector[] pts = getLine2D(1);
		PVector p1 = pts[0];
		PVector p2 = pts[1];
		return Line2D.ptLineDist(p1.x, p1.y, p2.x, p2.y, p.x, p.y);
		// return getLine2D(1).ptLineDist(p);
	}

	public PVector getPoint() {
		return convexHull.get(point);
	}

	void rotate(double rotAngle) {

		angle -= rotAngle;
		angle = angle % (Math.PI * 2.);

		deltaAngle -= rotAngle;

		if (deltaAngle <= 0) {

			point = (point + 1) % convexHull.size();
			double nextDeltaAngle = PVecMath2D.getAngleBetweenCCW(angle,
					angles[point]);

			deltaAngle = nextDeltaAngle;
		}

	}

}